import 'dart:math';
import 'package:flutter/material.dart';

import 'const.dart';

// 状态
enum Status {
  end,        // 已结束
  lost,       // 游戏失败
  playing,    // 游戏中
  won         // 游戏胜利
}

// 游戏
class Game {
  final double _mGameBoardSize;     // 游戏区域尺寸
  late List<List<int>> _mNumbers;   // 数字矩阵
  late Paint _mPaint;               // 绘图相关信息
  late Random _mRandom;             // 随机数生成器
  final State _mState;              // 界面状态
  late Status _mStatus;             // 游戏状态
  late TextPainter _mTextPainter;   // 文字绘图者
  late TextStyle _mTextStyle;       // 文字风格

  /// 构造方法。
  Game(this._mState, this._mGameBoardSize) {
    // 初始化成员
    _mNumbers = List.generate(Const.gameBoardGridCountV, (i) => List.generate(Const.gameBoardGridCountH, (j) => 0));
    _mPaint = Paint();
    _mRandom = Random();
    _mStatus = Status.end;
    _mTextPainter = TextPainter(
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr
    );
    _mTextStyle = TextStyle(
      color: Colors.black,
      fontSize: Const.gridSize * Const.scaleGameBoardTextSize
    );
  }

  /// 移动之后要做的事。
  /// @param  changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 true，否则为 false
  void _afterMove(bool changed) {
    // 如果胜利或失败，则游戏结束
    if (_isWon()) {
      _gameOver(Status.won);
      return;
    }
    if (_isLost()) {
      _gameOver(Status.lost);
      return;
    }

    // 创建下一个数
    if (changed) {
      _createRandomNumber();
    }
  }

  /// 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
  void _createRandomNumber() {
    var row = _mRandom.nextInt(Const.gameBoardGridCountV);
    var line = _mRandom.nextInt(Const.gameBoardGridCountH);

    while (_mNumbers[row][line] != 0) {
      row = _mRandom.nextInt(Const.gameBoardGridCountV);
      line = _mRandom.nextInt(Const.gameBoardGridCountH);
    }

    _mState.setState(() {
      _mNumbers[row][line] = _mRandom.nextInt(8) <= 3 ? 2: 4;
    });
  }

  /// 画所有数字。
  /// @param  drawer 绘图者
  void _drawAllNumbers(Canvas drawer) {
    for (var i = 0; i < Const.gameBoardGridCountV; i++) {
      for (var j = 0; j < Const.gameBoardGridCountH; j++) {
        if (_mNumbers[i][j] != 0) {
          _drawOneNumber(drawer, j, i, _mNumbers[i][j]);
        }
      }
    }
  }

  /// 画游戏内容。
  /// @param  drawer 绘图者
  void draw(Canvas drawer) {
    _drawGameBoard(drawer);
    _drawAllNumbers(drawer);
  }

  /// 画游戏区域。
  /// @param  drawer 绘图者
  void _drawGameBoard(Canvas drawer) {
    // 画边框
    _mPaint.style = PaintingStyle.stroke;
    _mPaint.strokeWidth = Const.gameBoardPenWidth;
    var rect = Rect.fromLTWH(1.0, 1.0, _mGameBoardSize, _mGameBoardSize);
    drawer.drawRect(rect, _mPaint);

    // 画背景
    _mPaint.color = Const.gameBoardColor;
    _mPaint.style = PaintingStyle.fill;
    rect = Rect.fromLTWH(Const.gameBoardPenWidth, Const.gameBoardPenWidth, _mGameBoardSize, _mGameBoardSize);
    drawer.drawRect(rect, _mPaint);

    // 画竖线
    _mPaint.color = Colors.black;
    _mPaint.style = PaintingStyle.stroke;
    int i;
    Offset start, end;
    for (i = 1; i < Const.gameBoardGridCountV; i++) {
      start = Offset((Const.gridSize + Const.gameBoardPenWidth) * i + Const.gameBoardPenWidth, Const.gameBoardPenWidth);
      end = Offset((Const.gridSize + Const.gameBoardPenWidth) * i + Const.gameBoardPenWidth, _mGameBoardSize + Const.gameBoardPenWidth);
      drawer.drawLine(start, end, _mPaint);
    }

    // 画横线
    for (i = 1; i < Const.gameBoardGridCountH; i++) {
      start = Offset(1.0, (Const.gridSize + Const.gameBoardPenWidth) * i + Const.gameBoardPenWidth);
      end = Offset(_mGameBoardSize + Const.gameBoardPenWidth, (Const.gridSize + Const.gameBoardPenWidth) * i + Const.gameBoardPenWidth);
      drawer.drawLine(start, end, _mPaint);
    }
  }

  /// 画单个数字。
  /// @param drawer 绘图者
  /// @param line   列
  /// @param row    行
  /// @param number 要画的数字
  void _drawOneNumber(Canvas drawer, int line, int row, int number) {
    // 设置属性
    _mPaint.style = PaintingStyle.fill;

    // 计算位置
    _mTextPainter.text = TextSpan(text: number.toString(), style: _mTextStyle);
    _mTextPainter.layout();
    final x = line * (Const.gridSize + Const.gameBoardPenWidth + 1) + (Const.gridSize - _mTextPainter.width) / 2;
    final y = row * (Const.gridSize + Const.gameBoardPenWidth + 1) + (Const.gridSize - _mTextPainter.height) / 2;

    // 画数字
    _mTextPainter.paint(drawer, Offset(x, y));
  }

  /// 游戏结束。
  /// @param status 游戏状态
  void _gameOver(Status status) {
    // 将状态设置为已结束
    _mState.setState(() {
      _mStatus = status;
    });
  }

  /// 判断是否失败。
  /// @return 失败则返回 true，否则返回 false
  bool _isLost() {
    int i, j;   // 循环变量

    // 横向检查所有数字
    for (i = 0; i < Const.gameBoardGridCountV; i++) {
      for (j = 0; j < Const.gameBoardGridCountH - 1; j++) {
        if (_mNumbers[i][j] == 0 || _mNumbers[i][j] == _mNumbers[i][j + 1]) {
          return false;
        }
      }
    }

    // 纵向检查所有数字
    for (j = 0; j < Const.gameBoardGridCountH; j++) {
      for (i = 0; i < Const.gameBoardGridCountV - 1; i++) {
        if (_mNumbers[i][j] == _mNumbers[i + 1][j]) {
          return false;
        }
      }
    }

    // 判断最右边一列是否有零
    for (i = 0; i < Const.gameBoardGridCountV; i++) {
      if (_mNumbers[i][3] == 0) {
        return false;
      }
    }

    return true;
  }

  /// 判断是否胜利。
  /// @return 胜利则返回 true，否则返回 false
  bool _isWon() {
    // 检查所有数字
    for (var i = 0; i < Const.gameBoardGridCountV; i++) {
      for (var j = 0; j < Const.gameBoardGridCountH; j++) {
        if (_mNumbers[i][j] == Const.winNumber) {
          return true;
        }
      }
    }

    return false;
  }

  /// 下移。
  void _moveDown() {
    int i, j, k;          // 循环变量
    var ignore = -1;      // 忽略的格
    var changed = false;  // 是否相加、移动

    // 从左到右遍历每一列
    for (j = 0; j < Const.gameBoardGridCountH; j++) {
      // 每列从上到下遍历每一格，判断是否有相同数，有则相加
      for (i = 0; i < Const.gameBoardGridCountV; i++) {
        // 如果该数为零或之前被加过，则什么也不做
        if (_mNumbers[i][j] == 0 || i == ignore) {
          continue;
        }

        // 不为零则从该数下方逐个检查
        for (k = i + 1; k < Const.gameBoardGridCountV; k++) {
          // 遇到零跳过
          if (_mNumbers[k][j] == 0) {
            continue;
          }

          // 如果两数不相等，则结束本次循环
          if (_mNumbers[k][j] != _mNumbers[i][j]) {
            break;
          }

          // 两数相等则相加
          _mNumbers[k][j] += _mNumbers[i][j];
          _mNumbers[i][j] = 0;
          ignore = k;
          changed = true;
        }
      }

      // 所有数字向下移动
      for (i = Const.gameBoardGridCountV - 2; i >= 0; i--) {
        // 如果该数为零（没有数），则什么也不做
        if (_mNumbers[i][j] == 0) {
          continue;
        }

        // 不为零则向下移动
        for (k = i + 1; k < Const.gameBoardGridCountV; k++) {
          if (_mNumbers[k][j] == 0) {
            _mNumbers[k][j] = _mNumbers[k - 1][j];
            _mNumbers[k - 1][j] = 0;
            changed = true;
          }
        }
      }
    }

    _afterMove(changed);
  }

  /// 左移。
  void _moveLeft() {
    int i, j, k;          // 循环变量
    var ignore = -1;      // 忽略的格
    var changed = false;  // 是否相加、移动

    // 从上到下遍历每一行
    for (i = 0; i < Const.gameBoardGridCountV; i++) {
      // 每行从右到左遍历每一格，判断是否有相同数，有则相加
      for (j = Const.gameBoardGridCountH - 1; j > 0; j--) {
        // 如果该数为零或之前被加过，则什么也不做
        if (_mNumbers[i][j] == 0 || j == ignore) {
          continue;
        }

        // 不为零则从该数左面逐个检查
        for (k = j - 1; k >= 0; k--) {
          // 遇到零跳过
          if (_mNumbers[i][k] == 0) {
            continue;
          }

          // 如果两数不相等，则结束本次循环
          if (_mNumbers[i][k] != _mNumbers[i][j]) {
            break;
          }

          // 两数相等则相加
          _mNumbers[i][k] += _mNumbers[i][j];
          _mNumbers[i][j] = 0;
          ignore = k;
          changed = true;
        }
      }

      // 所有数字向左移动
      for (j = 1; j < Const.gameBoardGridCountH; j++) {
        // 如果该数为零（没有数），则什么也不做
        if (_mNumbers[i][j] == 0) {
          continue;
        }

        // 不为零则向左移动
        for (k = j - 1; k >= 0; k--) {
          if (_mNumbers[i][k] == 0) {
            _mNumbers[i][k] = _mNumbers[i][k + 1];
            _mNumbers[i][k + 1] = 0;
            changed = true;
          }
        }
      }
    }

    _afterMove(changed);
  }

  /// 右移。
  void _moveRight() {
    int i, j, k;          // 循环变量
    var ignore = -1;      // 忽略的格
    var changed = false;  // 是否相加、移动

    // 从上到下遍历每一行
    for (i = 0; i < Const.gameBoardGridCountV; i++) {
      // 每行从左到右遍历每一格，判断是否有相同数，有则相加
      for (j = 0; j < Const.gameBoardGridCountH - 1; j++) {
        // 如果该数为零或之前被加过，则什么也不做
        if (_mNumbers[i][j] == 0 || j == ignore) {
          continue;
        }

        // 不为零则从该数右面逐个检查
        for (k = j + 1; k < Const.gameBoardGridCountH; k++) {
          // 遇到零跳过
          if (_mNumbers[i][k] == 0) {
            continue;
          }

          // 如果两数不相等，则结束本次循环
          if (_mNumbers[i][k] != _mNumbers[i][j]) {
            break;
          }

          // 两数相等则相加
          _mNumbers[i][k] += _mNumbers[i][j];
          _mNumbers[i][j] = 0;
          ignore = k;
          changed = true;
        }
      }

      // 所有数字向右移动
      for (j = Const.gameBoardGridCountH - 2; j >= 0; j--) {
        // 如果该数为零（没有数），则什么也不做
        if (_mNumbers[i][j] == 0) {
          continue;
        }

        // 不为零则向右移动
        for (k = j + 1; k < Const.gameBoardGridCountH; k++) {
          if (_mNumbers[i][k] == 0) {
            _mNumbers[i][k] = _mNumbers[i][k - 1];
            _mNumbers[i][k - 1] = 0;
            changed = true;
          }
        }
      }
    }

    _afterMove(changed);
  }

  /// 上移。
  void _moveUp() {
    int i, j, k;          // 循环变量
    var ignore = -1;      // 忽略的格
    var changed = false;  // 是否相加、移动

    // 从左到右遍历每一列
    for (j = 0; j < Const.gameBoardGridCountH; j++) {
      // 每列从下到上遍历每一格，判断是否有相同数，有则相加
      for (i = Const.gameBoardGridCountV - 1; i > 0; i--) {
        // 如果该数为零或之前被加过，则什么也不做
        if (_mNumbers[i][j] == 0 || i == ignore) {
          continue;
        }

        // 不为零则从该数上方逐个检查
        for (k = i - 1; k >= 0; k--) {
          // 遇到零跳过
          if (_mNumbers[k][j] == 0) {
            continue;
          }

          // 如果两数不相等，则结束本次循环
          if (_mNumbers[k][j] != _mNumbers[i][j]) {
            break;
          }

          // 两数相等则相加
          _mNumbers[k][j] += _mNumbers[i][j];
          _mNumbers[i][j] = 0;
          ignore = k;
          changed = true;
        }
      }

      // 所有数字向上移动
      for (i = 1; i < Const.gameBoardGridCountV; i++) {
        // 如果该数为零（没有数），则什么也不做
        if (_mNumbers[i][j] == 0) {
          continue;
        }

        // 不为零则向上移动
        for (k = i - 1; k >= 0; k--) {
          if (_mNumbers[k][j] == 0) {
            _mNumbers[k][j] = _mNumbers[k + 1][j];
            _mNumbers[k + 1][j] = 0;
            changed = true;
          }
        }
      }
    }

    _afterMove(changed);
  }

  /// 滑动手势的响应方法。
  /// @param dragEndDetails 手势事件的详细信息
  void onFling(DragEndDetails dragEndDetails) {
    if (_mStatus != Status.playing) {
      return;
    }

    final x = dragEndDetails.velocity.pixelsPerSecond.dx;
    final y = dragEndDetails.velocity.pixelsPerSecond.dy;
    if (x.abs() > y.abs()) {
      if (x < 0) {
        _moveLeft();
      } else {
        _moveRight();
      }
    } else if (y < 0) {
      _moveUp();
    } else {
      _moveDown();
    }
  }

  /// 游戏开始。
  void start() {
    // 数字矩阵清零
    for (var i = 0; i < Const.gameBoardGridCountV; i++) {
      for (var j = 0; j < Const.gameBoardGridCountH; j++) {
        _mNumbers[i][j] = 0;
      }
    }

    // 创建随机数字
    _createRandomNumber();

    // 将状态设置为游戏中
    _mState.setState(() {
      _mStatus = Status.playing;
    });
  }

  /// 获取游戏状态。
  Status get status => _mStatus;
}
